package rj.cometome;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;

final public class LocationsHandler implements SensorEventListener, LocationListener {
	private static LocationsHandler instance;
	
	public class CircularBuffer {
		private int size;
		private int current;
		private Point3d[] values;
		private boolean isFull;
		
		public CircularBuffer(int size) {
			this.size = size;
			this.current = 0;
			this.isFull = false;
			this.values = new Point3d[size];
		}
		
		public void push(Point3d point) {
			values[current] = new Point3d(point);
			current++;
			if (current >= size) {
				isFull = true;
				current = 0;
			}
		}
		
		public float getX() {
			float sum = 0;
			
			if (!isFull) return values[0].x;
			
			for (int i = 0; i < size; ++i) {
				sum += values[i].x;
			}
			
			return sum / size;
		}
		
		public float getY() {
			float sum = 0;
			
			if (!isFull) return values[0].y;
			
			for (int i = 0; i < size; ++i) {
				sum += values[i].y;
			}
			
			return sum / size;
		}
		
		public float getZ() {
			float sum = 0;
			
			if (!isFull) return values[0].z;
			
			for (int i = 0; i < size; ++i) {
				sum += values[i].z;
			}
			
			return sum / size;
		}
	};
	
	Location lastLocation;
	Location pinnedLocation;
	SensorManager m_smManager;
	LocationManager m_lManager;
	Sensor m_sAccel;
	Sensor m_sCompass;
	CircularBuffer m_gravBuffer = new CircularBuffer(10);
	CircularBuffer m_geoBuffer = new CircularBuffer(10);
	
	float m_fYaw;
	float m_fPitch;
	float m_fRoll;
	
	float[] rotationMatrix = new float[16];
	
	private LocationsHandler() {
		m_smManager = (SensorManager)Utils.getContext().getSystemService(Context.SENSOR_SERVICE);
		m_lManager = (LocationManager)Utils.getContext().getSystemService(Context.LOCATION_SERVICE);
		m_sAccel = m_smManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		m_sCompass = m_smManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
		
		Criteria criteria = new Criteria();
		criteria.setAccuracy(Criteria.ACCURACY_FINE);
		criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
		
		String bestProvider = m_lManager.getBestProvider(criteria, true);
		m_lManager.requestLocationUpdates(bestProvider, 0, 50, this);
	}

	public synchronized static LocationsHandler getInstance() {
		if (instance == null)
			instance = new LocationsHandler();
		
		return instance;
	}
	
	public synchronized void registerListeners() {
		m_smManager.registerListener(this, m_sAccel, SensorManager.SENSOR_DELAY_GAME);
		m_smManager.registerListener(this, m_sCompass, SensorManager.SENSOR_DELAY_GAME);
	}
	
	public synchronized void unregisterListeners() {
		m_smManager.unregisterListener(this);
	}
	
	@Override
	public void onAccuracyChanged(Sensor arg0, int arg1) {
	}

	float[] gravity;
	float[] geomagnetic;
	
	@Override
	public void onSensorChanged(SensorEvent arg0) {
		DebugView v = Utils.getDebugView();
		
		if (v == null) return;
		
		if (arg0.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
			if (gravity == null) gravity = new float[3];
			m_gravBuffer.push(new Point3d(arg0.values));
		}
		if (arg0.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
			if (geomagnetic == null) geomagnetic = new float[3];
			m_geoBuffer.push(new Point3d(arg0.values));
		}
		
		if (gravity != null && geomagnetic != null) {
			float inclination[] = new float[9];
			
			gravity[0] = m_gravBuffer.getX();
			gravity[1] = m_gravBuffer.getY();
			gravity[2] = m_gravBuffer.getZ();
			geomagnetic[0] = m_geoBuffer.getX();
			geomagnetic[1] = m_geoBuffer.getY();
			geomagnetic[2] = m_geoBuffer.getZ();
			
			SensorManager.getRotationMatrix(rotationMatrix, inclination, gravity, geomagnetic);
			SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrix);
			
			float[] values = new float[3];
			SensorManager.getOrientation(rotationMatrix, values);
			m_fYaw = values[0];
			m_fPitch = values[1];
			m_fRoll = values[2];
		}
	}
	
	public float[] getRotation() {
		float[] rtn = new float[3];
		rtn[0] = m_fYaw;
		rtn[1] = m_fPitch;
		rtn[2] = m_fRoll;
		return rtn;
	}

	@Override
	public void onLocationChanged(Location arg0) {
		lastLocation = arg0;
	}

	@Override
	public void onProviderDisabled(String arg0) {
	}

	@Override
	public void onProviderEnabled(String arg0) {
	}

	@Override
	public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
	}
	
	public void pinLocation() {
		pinnedLocation = lastLocation;
	}
	
	public float getBearing() {
		if (lastLocation == null) return 0;
		
		return lastLocation.bearingTo(pinnedLocation);
	}
}